package htmlparser;

import java.io.IOException;
import java.io.InputStream;
//import java.io.StringReader;
import java.text.Normalizer;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
//import javax.xml.parsers.DocumentBuilder;
//import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.DomSerializer;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
//import org.xml.sax.InputSource;
//import org.xml.sax.SAXException;


/**
 * HTML parser class parsing informations from <a href="www.eurofotbal.cz">www.eurofotbal.cz</a> server
 *
 * @version 20110503
 * @author Peter Jurkovic
 */

public final class HtmlParser {

    Document document;
    
    /**
     * Convert (HTML) InputStream to the DOM and set the Document
     *
     * @param data InputStream HTML content
     * @throws IOException if some I/O error occurred
     * @throws IllegalArgumentException if is InputStream empty
     */
    public HtmlParser(InputStream data) throws IOException{
        setDocument(data);       
    }


    // --------------------------------------------------------------------------- 
    /**
     * Returns parsed links to mathes from <a href="www.eurofotbal.cz">www.eurofotbal.cz</a> server.
     *
     * Match list URL examle: <a href="http://www.eurofotbal.cz/corgon-liga/2010-2011/vysledky-rozlosovani/?month=0">http://www.eurofotbal.cz/corgon-liga/2010-2011/vysledky-rozlosovani/?month=0</a>.
     * @param domainName String domain name. Example : http://domain.tld
     * @return Unmodifiable Set<String> of parsed links
     * @throws IllegalArgumentException if is not set documenet or domain name.
     */
    public Map<String, String> extractLinks(String domainName){
        if(document == null){
            throw new IllegalArgumentException("Data for processing are unavailable.");
        }
        if(domainName == null){
            throw new IllegalArgumentException("Doman name is not set.");
        }
        Map<String, String> links = new TreeMap<String, String>(); //tree map to auto-sort by key
  
        NodeList nodes = executeQuery("//div[@class='col-center']//table[@class='matches']//td[@class='teams']/a");
            
        for (int i = 0; i < nodes.getLength(); i++) {
         Element link = (Element) nodes.item(i);
         Node teamsName = nodes.item(i).getParentNode();
         Node date = teamsName.getParentNode();
         String id = generateId(date.getTextContent(), teamsName.getTextContent());
         links.put(id, domainName + link.getAttribute("href"));
        }
        return Collections.unmodifiableMap(links);
    }
    
    
    // ---------------------------------------------------------------------------
    /**
     * The method parse html document from eurofotabl.cz server,
     * select information of given match and return new XML Document
     * with info.
     *
     * XML structure of Document: <a href="http://pastebin.com/K3JUjD8S">http://pastebin.com/K3JUjD8S</a>
     * @return Well-formed XML String with math info.
     * @throws HtmlParserException if parsing failed
     * @throws IllegalArgumentException if is not set document to parse.
     */
    public  String extractMatchInfo() throws HtmlParserException{

     
           String[] dateTime = getDateTime();
           String[] teams = getTeamNames();
           String[] ls = getLeagueNameAndSeason();
           String[] players = getXMLChanges();
           String round = getRound();
           String score = getXMLScore();
           String goals = getXMLGoals();
           String carts = getXMLCarts();
           String referee = getMatchStatsRowContent(6);
           String stadium = getStadium();
           String spec = getMatchStatsRowContent(7);
           if(dateTime == null || teams == null){
               throw new HtmlParserException("Parsing failed");
           }
         String strDocument = "<match id=\"" + generateId(dateTime[0],teams[0]+"-"+teams[1]) + "\" >" 
                + (ls[0]!=null ? "<league>" + ls[0] + "</league>" : "")
                + (ls[1]!=null ? "<season>" + ls[1] + "</season>" : "" )
                + (round != null ? "<round>"+ getRound()+ "</round>" : "")
                + "<date>"+ dateTime[0] +"</date>"
                + "<time>" + dateTime[1] + "</time>"
                + (teams != null ? "<home>"+ teams[0] +"</home>"
                                 + "<guests>" + teams[1] +"</guests>" : "" )
                + (score != null ? score : "") 
                + (goals != null ? goals : "") 
                + (carts != null ? carts : "")
                + (players[0] != null ? players[0] : "")
                + (players[1] != null ? players[1] : "")
                + (referee != null ? "<referee>" + referee + "</referee>" : "" )
                + (stadium != null ? "<stadium>"+ stadium +"</stadium>" : "")
                + (spec != null ? "<spectators>" + spec + "</spectators>" : "" )
                + "</match>"; 
         

        
        /*DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = null;
        try {
            builder = factory.newDocumentBuilder();
            return builder.parse(new InputSource(new StringReader(strDocument)));
        }catch (ParserConfigurationException ex) {
            Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SAXException ex) {
            Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;*/
        
        return strDocument;
    }
   
   

    // ---------------------------------------------------------------------------
    /**
     * Generate ID of given match
     *
     * @param String date Date of match
     * @param String teams in shape (home - guests)
     * @return String ID
     * @throws IllegalArgumentException if date or teams are not set.
     */
    private String generateId(String date, String teams){
        if(date == null || teams == null){
            throw new IllegalArgumentException("Can not generet match ID.");
        }
        char day[] = {date.charAt(0),date.charAt(1) };
        char month[] = {date.charAt(3),date.charAt(4) };
        char year[] = {date.charAt(6),date.charAt(7),date.charAt(8),date.charAt(9) };
        String id = new String(year)+new String(month)+new String(day)+"-" +
        Normalizer.normalize(teams, Normalizer.Form.NFD)
                .replaceAll("[^\\p{ASCII}]", "");
       return id.replace(" ", "-").toLowerCase().replaceAll("\\-+","-");
   }
    
    
   // ---------------------------------------------------------------------------
    /**
     * Convert (HTML) InputStream to the DOM and set the Document
     * 
     * @param data InputStream HTML content
     * @throws IOException if some I/O error occurred
     * @throws IllegalArgumentException if is InputStream empty
     */

    public void setDocument(InputStream data) throws IOException{
        if(data == null){
            throw new IllegalArgumentException("Data for processing are unavailable.");
        }
        HtmlCleaner cleaner = new HtmlCleaner();
        TagNode rootNode = cleaner.clean(data, "UTF-8");
        try {
            document = new DomSerializer(new CleanerProperties()).createDOM(rootNode);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns time and date of parsed match.
     *
     * @return String[] in index 0 is date of match and in index 1 is time of match. If
     * parsing fials, return null.
     */
    public String[] getDateTime(){
        NodeList nodes = executeQuery("//div[@class='col-center']//div[@class='date']");   
        if(nodes.getLength() == 0){
            return null;
        }
        String dateTime = nodes.item(0).getTextContent();
        String[] data = null;
        if(dateTime.length() > 16){
            data = dateTime.substring(0, 16).split(" ",2);
        }
        return data;
    }


    // ---------------------------------------------------------------------------
    /**
     * Returns team names of match.
     *
     * @return String[] In zero index in array is name of home team and in 1-index is name of guest team.
     * If parsing fials, return null.
     */
    public String[] getTeamNames(){
        NodeList nodes = executeQuery("//div[@class='content']//h2/a");
        if(nodes.getLength() != 2){
            return null;
        }
        String[] data = 
                { nodes.item(0).getTextContent().trim(),
                  nodes.item(1).getTextContent().trim()
                };
        return data;
    }
    
    
   // ---------------------------------------------------------------------------
   /**
     * Returns league name and season of match
     * 
     * @return String[] In 0-index is league name and in 1-index season of match. 
     * If parsing fials, return null.
     */ 
   public String[] getLeagueNameAndSeason(){
       
        NodeList nodes = executeQuery("//div[@class='middle']//h1");

        if(nodes.getLength() != 1){
            return null;
        }
        
        String str = nodes.item(0).getTextContent().trim();
        String[] data = null;
        if(str.length() > 10){
             data = new String[2];
             data[0] = str.substring( (str.indexOf("-")+2), (str.indexOf("20")-1));
             data[1] = str.substring( (str.indexOf("20")));    
        }
   
        return data;
   }
    
   
   
    // ---------------------------------------------------------------------------
   /**
    * Returns round number of match
    * @return String round no. of match. If parsing fials, return null.
    */
    public String getRound(){
       NodeList nodes = executeQuery("//div[@class='middle']//div[@class='stage']");
       if(nodes.getLength() == 1){
           return nodes.item(0).getTextContent().substring(0, 1);
       }
       return null;
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns the name of the stadium
     * 
     * @return String stadium name, otherwise null.
     */
    public String getStadium(){
       NodeList nodes = executeQuery("//div[@class='stadium']//a");
       if(nodes.getLength() == 1){
           return nodes.item(0).getTextContent().trim();
       }
       return null;
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns match score in XML shape.<br /><br />
     * 
     * example: <br />
     * &lt;score&gt;<br />
     * &lt;half home=&quot;0&quot; guests=&quot;0&quot;/&gt;<br />
     * &lt;full home=&quot;1&quot; guests=&quot;0&quot;/&gt;<br />
     * &lt;/score&gt;<br />
     * 
     * @return String match score in xml shape, otherwise null.
     */
    public String getXMLScore(){
       NodeList nodes = executeQuery("//div[@class='content']//td[@class='nb result']");
       if(nodes.getLength() == 1){
           String[] sc =  nodes.item(0).getTextContent().split("\\(");
           String[] full = sc[0].split(":");
           String[] half = sc[1].replace(")", "").split(":");
           return "<score>"+
                  "<half home=\"" + half[0].trim() + "\" guests=\"" + half[1].trim() + "\"/>" +
                  "<full  home=\"" + full[0].trim() + "\" guests=\"" + full[1].trim() + "\"/>" +
                  "</score>";
       }
       return null;
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns informations about goals who fell in the match in XML shape<br/>
     * 
     * example:<br/>
     * &lt;goals&gt;<br/>
     * &lt;goal team=&quot;1&quot; minute=&quot;64&quot; player=&quot;Coimbra Higor de Sales&quot;/&gt;<br/>
     * &lt;/goals&gt;<br/>
     * @return String goal info from match in XML shape, or NULL if not found
     */
    public String getXMLGoals(){
       NodeList nodes = executeQuery("//table[@class='matchstats']//tr[1]/td[contains(@class,'data')]/a");
       String xmlResult = "";
       
       if(nodes.getLength() == 0){
           return null;
       }
       // first replaceAll removes (pen.)
       String d = nodes.item(0).getParentNode().getParentNode().getTextContent().replaceAll("\\(.+\\)", "").replaceAll("[^\\d\\.]", "");

       String[] mins = d.split("\\.");
       
       if(mins.length == 0){
           return null;
       }
       
       //int homeTeamGoals = nodes.item(0).getParentNode().getTextContent().replaceAll("\\(.+\\)", "").replaceAll("[^.]", "").length();
       int homeTeamGoals = executeQuery("//table[@class='matchstats']//tr[1]/td[@class='data']/a").getLength();
       for(int i=0;i < mins.length;i++){
           xmlResult += "<goal team=\"" + (i<homeTeamGoals ? "1" : "2")
                        + "\" minute=\""+mins[i]+"\" player=\""
                        + nodes.item(i).getTextContent()+ "\" />";
       }
       return (xmlResult.length() != 0 ? "<goals>"+xmlResult+"</goals>" : null);
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns informations about the carts in match.
     * 
     * @return String informations about the carts in match<br/>
     * Example:<br/>
     * 
     * &lt;cards&gt;<br/>
     * &lt;card type=&quot;yellow&quot; team=&quot;1&quot; minute=&quot;65&quot; player=&quot;Anton Sloboda&quot;/&gt;<br/>
     * &lt;card type=&quot;yellow&quot; team=&quot;2&quot; minute=&quot;22&quot; player=&quot;Pavol Ďurica&quot;/&gt;<br/>
     * &lt;/cards&gt;<br/>
     */
    public String getXMLCarts(){
       String xmlResult = "";
       for(int i =2; i <= 3;i++ ){
           NodeList nodes = executeQuery("//table[@class='matchstats']//tr["
                                          + i+"]/td[contains(@class,'data')]/a");

           if(nodes.getLength() == 0){
               continue;
           }

           String d = nodes.item(0).getParentNode().getParentNode().getTextContent().replaceAll("[^\\d\\.]", "");
           String[] min = d.split("\\.");
           if(min.length == 0){
               continue;
           }
           //int homeTeamCarts = d.replaceAll("[^.]", "").length();
           int homeTeamCarts = executeQuery("//table[@class='matchstats']//tr["+i+"]/td[@class='data']/a").getLength();
           xmlResult += cart((i <= 2 ? "yellow" : "red" ), homeTeamCarts , min, nodes);

       }
       return (xmlResult.length()!=0 ? "<cards>"+xmlResult+"</cards>" : null);
    }
    
    private String cart(String color, int homeTeamCarts, String[] min, NodeList nodes ){
        
        String xmlResult = "";
        
        if(min.length == 0 || nodes.getLength() == 0 || (min.length != nodes.getLength())){
            return null;
        }
        for(int i=0;i < min.length;i++){
           xmlResult += "<card type=\""+color+"\" team=\""
                     + (i < homeTeamCarts ? "1" : "2") 
                     + "\" minute=\"" + min[i] + "\" player=\""
                     + nodes.item(i).getTextContent()  + "\" />";
        }
        return xmlResult;
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns exchange of players in the match in XML shape
     * 
     * @return String exchange of players in XML shape, null otherwise<br/>
     * Example:<br/>
     * &lt;changes&gt;<br/>
     * &lt;change team=&quot;1&quot; minute=&quot;84&quot; in=&quot;Vladim&iacute;r 
     * Kov&aacute;č&quot; out=&quot;Tom&aacute;&scaron; Ďubek&quot;/&gt;<br/>
     * &lt;/changes&gt;
     */
    public String[] getXMLChanges(){
       String strChanges = "";
       String strPlayers = "";
       String[] result = new String[2];
      for(int j =4; j <= 5;j++ ){
         NodeList nodes = executeQuery("//table[@class='matchstats']//tr["+j+"]/td/a");
         if(nodes.getLength() == 0){
             return null;
         }
         String allPlayers = nodes.item(0).getParentNode().getTextContent();
           
         allPlayers = allPlayers.substring(allPlayers.indexOf(":")+1, allPlayers.length());
         
         String[] players = allPlayers.split(","); 
         for(int i=0;i< players.length;i++){
              if(players[i].contains("(")){
                 String[] changes = players[i].split("\\(");
                 if(changes.length == 2){
                      String[] in = changes[1].split("\\.");
                        if(in.length == 2){
                           strChanges += "<change team=\""
                                    + (j<=4 ? "1" : "2") +"\" minute=\""
                                    + in[0]+"\" in=\"" 
                                    + in[1].substring(0, in[1].length()-1) 
                                    + "\" out=\""+changes[0].trim()+"\"/>";
                        }
                 }
              }
           }
         strPlayers += (j==4 ? "<home-team>" : "<guest-team>");
         for(int i=0;i< nodes.getLength();i++){
            strPlayers +=  "<player name=\""+ nodes.item(i).getTextContent() +"\"/>";  
         }
         strPlayers += (j==4 ? "</home-team>" : "</guest-team>");
      }
          
          //xmlResult = "";
          //
          result[0] = ((strChanges.length()!=0) ? "<changes>"+strChanges+"</changes>" : null);
          result[1] = ((strPlayers.length()!=0) ? strPlayers : null);
       return result;
    }
  
    
    
    // ---------------------------------------------------------------------------
    /**
     * Returns the content of table cell (with attribute class = matches) by index
     * 
     * @param index Index of table cell
     * @return content of table cell by index, null otherwise
     */
    public String getMatchStatsRowContent(int index){
        NodeList nodes = executeQuery("//table[@class='matchstats']//tr["+ index +"]/td");
        
        if(nodes.getLength() == 0){
            return null;
        }
        String str = nodes.item(0).getTextContent();
        
        if(str.length() == 0){
            return null;
        }
        if (index == 7) { //number of spectators, remove the dot and non-breaking space
            return str.substring(str.indexOf(":")+2,str.length()).trim().replace(".", "");
        } else {
            return str.substring(str.indexOf(":")+2,str.length()).trim();
        }
    }
    
    
    
    // ---------------------------------------------------------------------------
    /**
     * The method execute XPath query by given expression
     * 
     * @param expression xPath expression
     * @return NodeList 
     * @throws IllegalArgumentException if is not set document or expression
     */
    public NodeList executeQuery(String expression){
       
       if(document == null || expression == null){
           throw new IllegalArgumentException("XPath query can not be executed.");
       }
        
       try {
            XPath xpath = XPathFactory.newInstance().newXPath();
            return (NodeList) xpath.evaluate(expression, document, XPathConstants.NODESET);
       } catch (XPathExpressionException ex) {
            Logger.getLogger(HtmlParser.class.getName()).log(Level.SEVERE, null, ex);
       }
       return null;
    }
    

}
